<?php

/**
 * @user magein
 * @date 2023/12/1 13:32
 */

namespace magein\sms\platform;

use magein\utils\Result;
use magein\sms\SmsCode;
use magein\sms\Utils;
use Psr\Http\Message\ResponseInterface;

class Ali extends Platform
{
    /**
     * @var string
     */
    protected $access_key_id = '';

    /**
     * @var string
     */
    protected $access_key_secret = '';

    /**
     * @var string
     */
    protected $signature = '';

    /**
     * @var string
     */
    protected $algorithm = 'ACS3-HMAC-SHA256';

    /**
     * @return string
     */
    public function version(): string
    {
        return 'v3';
    }

    /**
     * @return Result
     */
    public function code(): Result
    {
        $code_template_id = $this->target->getTemplateId();
        if (empty($code_template_id)) {
            $this->target->setTemplateId($this->config['code_template_id'] ?? '');
        }

        $content = $this->target->getContent();
        if (preg_match('/^\d+$/', $content)) {
            $content = "{code:$content}";
        }
        $this->target->setContent($content);

        return $this->send();
    }

    /**
     * @return Result
     */
    public function send(): Result
    {

        $this->request();

        $content = $this->toArray();

        if (empty($content)) {
            return Result::error($this->error ?: '短信平台请求异常', SmsCode::SMS_PLATFORM_EXCEPTION);
        }


        /**
         * $content 返回值
         *
         * {
         *  "Code": "OK",
         *  "Message": "OK",
         *  "BizId": "9006197469364984****",
         *  "RequestId": "F655A8D5-B967-440B-8683-DAD6FF8DE990"
         * }
         */

        $code = $content['Code'] ?? [];
        if ($code == 'OK') {
            return Result::success($content);
        }

        return Result::error($content['Message'] ?? '', SmsCode::SMS_SEND_FAIL);
    }

    /**
     * @param $params
     * @return void
     */
    public function init($params)
    {
        $this->access_key_id = $params['access_key_id'];
        $this->access_key_secret = $params['access_key_secret'];
        $this->signature = $params['signature'];
    }

    /**
     * @return ResponseInterface|null
     */
    protected function request(): ?ResponseInterface
    {
        $endpoint = 'dysmsapi.aliyuncs.com';
        // API 版本
        $version = '2017-05-25';
        // API的名称
        $action = 'SendSms';

        $phone = $this->target->getPhone();
        $body = [
            'PhoneNumbers' => $phone,
            'SignName' => $this->signature,
            'TemplateCode' => $this->target->getTemplateId(),
        ];
        $body['TemplateParam'] = $this->target->getContent();

        // V3 参考文档 https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature

        /**
         * 规范化请求头（CanonicalizedHeaders）是非标准HTTP头部信息,是指请求中出现的以x-acs-为前缀的参数信息，构造方法如下：
         * 1. 将所有需要签名的消息头的名称转换为小写。
         * 2. 将消息头的值除去首尾空格。对于有多个值的消息头，将多个值分别除去首尾空格后按值升序排列，然后用逗号（,）连接。
         * 3. 将步骤1、2的结果以英文冒号（:）连接，并在尾部添加换行符，组成一个规范化消息头(CanonicalHeaderEntry)。
         * 4. 多个规范化消息头，按照消息头名称（小写）的字符代码顺序以升序排列后拼接在一起。
         * 5. 如果没有需要签名的消息头，使用空字符串作为规范化消息头列表。
         */

        $timestamp = Utils::getTimestamp();
        $nonce = Utils::getNonce();
        $payload = Utils::hexEncodeHashSha256(Utils::toBytes(""));

        $request_headers = [
            'host' => $endpoint,
            'x-acs-action' => $action,
            'x-acs-version' => $version,
            'x-acs-date' => $timestamp,
            'x-acs-signature-nonce' => $nonce,
            'x-acs-content-sha256' => $payload,
            'accept' => 'application/json'
        ];
        // 步骤四：将签名添加到请求中
        $request_headers['Authorization'] = $this->getAuthorization($request_headers, $body, $payload);

        return $this->post('https://' . $endpoint, [
            'headers' => $request_headers,
            'query' => $body
        ]);
    }

    /**
     * @param $request_headers
     * @param $query
     * @param $payload
     * @return string
     */
    public function getAuthorization($request_headers, $query, $payload)
    {
        $canonical_url = '/';
        $method = 'POST';
        $canonicalQueryString = self::getCanonicalQueryString($query);

        $sign_headers = [];
        foreach ($request_headers as $k => $v) {
            $k = strtolower($k);
            if (0 === strpos($k, 'x-acs-') || 'host' === $k || 'content-type' === $k) {
                $sign_headers[$k] = $v;
            }
        }
        ksort($sign_headers);

        $headers = [];
        foreach ($request_headers as $k => $v) {
            $k = strtolower($k);
            if (0 === strpos($k, 'x-acs-') || 'host' === $k || 'content-type' === $k) {
                $headers[$k] = trim($v);
            }
        }

        $canonicalHeaderString = '';
        ksort($headers);
        foreach ($headers as $k => $v) {
            $canonicalHeaderString .= $k . ':' . trim($this->filter($v)) . "\n";
        }
        if (empty($canonicalHeaderString)) {
            $canonicalHeaderString = "\n";
        }

        $canonicalRequest = $method . "\n"
            . $canonical_url . "\n"
            . $canonicalQueryString . "\n"
            . $canonicalHeaderString . "\n" . implode(';', array_keys($sign_headers)) . "\n" . $payload;
        $str_to_sign = $this->algorithm . "\n" . Utils::hexEncodeHashSha256($canonicalRequest);
        $signature = $this->sign($str_to_sign);
        $signature = Utils::hexEncode($signature);

        return $this->algorithm .
            ' Credential=' . $this->access_key_id .
            ',SignedHeaders=' . implode(';', array_keys($sign_headers)) .
            ',Signature=' . $signature;
    }

    /**
     * @param $str
     * @return array|string
     */
    public function sign($str)
    {
        $result = hash_hmac('sha256', $str, $this->access_key_secret, true);

        return Utils::toBytes($result);
    }

    /**
     * @param $query
     * @return string
     */
    private function getCanonicalQueryString($query): string
    {
        ksort($query);

        $params = [];
        foreach ($query as $k => $v) {
            if (null === $v) {
                continue;
            }
            $str = rawurlencode($k);
            if ('' !== $v && null !== $v) {
                $str .= '=' . rawurlencode($v);
            } else {
                $str .= '=';
            }
            $params[] = $str;
        }

        return implode('&', $params);
    }

    /**
     * @param $str
     * @return array|string|string[]
     */
    private function filter($str)
    {
        return str_replace(["\t", "\n", "\r", "\f"], '', $str);
    }
}